Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Falcon2001
Oct 10, 2004

Eat your hamburgers, Apollo.
Pillbug

haruspicy posted:

Whoever in here said that they didn’t really get Python until reading Fluent Python goddamn is that ever true, just picked it up

Well that's a hell of an endorsement, gonna pick it up.

Adbot
ADBOT LOVES YOU

Colonel Taint
Mar 14, 2004


I've been banging my head against the wall here for a while... I'm trying to look through some stock market minute data. The data I have includes extended hours, and I want to get only minutes where the market is open. There's a module pandas_market_calendars that provides a function, I'm currently trying to get it set up like so:

Python code:
aapl_hist = loadDf(["AAPL"], "5m_db")
## aapl_hist is multi-index of ("AAPL", timestamp)
histidx = aapl_hist.index.get_level_values(1).to_series()
# histidx.head()
# t
# 2021-10-04 08:00:00+00:00   2021-10-04 08:00:00+00:00
# 2021-10-04 08:05:00+00:00   2021-10-04 08:05:00+00:00
# 2021-10-04 08:10:00+00:00   2021-10-04 08:10:00+00:00
# 2021-10-04 08:15:00+00:00   2021-10-04 08:15:00+00:00
# 2021-10-04 08:20:00+00:00   2021-10-04 08:20:00+00:00
# Name: t, dtype: datetime64[ns, UTC]

nyse_cal = pmc.get_calendar("NYSE")
schedule = nyse_cal.schedule(histidx.min(), histidx.max())

#Basic usage, works:
nyse_cal.open_at_time(schedule,  datetime.datetime(2023, 4, 5, 15, 4, tzinfo=timezone.utc))

def get_market_open_fn(cal, schedule):
    def market_open_fn(n):
        print(n)
        return cal.open_at_time(schedule, n)
    return market_open_fn

hFilter = histidx.apply(get_market_open_fn(nyse_cal, schedule))

#On calling apply, throws:
#TypeError: Cannot convert input [DatetimeIndex( ... dtype='datetime64[ns, UTC]', length=71722, freq=None)] of type <class 'pandas.core.indexes.datetimes.DatetimeIndex'> to Timestamp

When I call apply, it looks like the whole index is being passed to the market_open_fn somehow? I'm still pretty new to pandas so maybe I'm missing something obvious here? All I want is a T/F so I can filter the original history time series (aapl_hist in this case).

Also, dealing with timestamps/time zones is one of the most infuriating programming things I've encountered probably ever.

e: Figured it out... sort of. The calendar schedule class was rejecting the first few timestamps with ValueError because the min time in the schedule is actually the first open time on the date given as min in the schedule creation, and the minimum in the aapl data was pre-market. So even though giving `histidx.min()` should have - in my mind - set the schedule to include the pre-market times, the schedule did not include them. This was raising a ValueError though, not a TypeError. Changing the market_open_fn code to this worked:

Python code:
def get_market_open_fn(cal, schedule):
    def market_open_fn(n):
        # print(n, type(n))
        ret = False
        try: 
            ret = cal.open_at_time(schedule, n)
        except ValueError as e:
            ret = False
        return ret
            
    return market_open_fn
I don't know what happened to the original TypeError though. This turned out to be terribly slow (2 min. to get the filter series for ~70k rows), so I'm going to have to re-think my approach here anyway.

Colonel Taint fucked around with this message at 16:57 on Apr 11, 2023

a dingus
Mar 22, 2008

Rhetorical questions only
Fun Shoe
I'll also give Fluent Python a great endorsement. I read it when I was a late beginner and felt like it made me an intermediate just by how it connected so many dots. It was something I referenced often and still would if I were doing python full time.

If anyone knows the same type of book for JS/TS I'm all ears!

DoctorTristan
Mar 11, 2006

I would look up into your lifeless eyes and wave, like this. Can you and your associates arrange that for me, Mr. Morden?
If you want .apply() to act on every row in that series you’ll need to do .apply(…, axis=1).

Colonel Taint
Mar 14, 2004


DoctorTristan posted:

If you want .apply() to act on every row in that series you’ll need to do .apply(…, axis=1).

The apply is running on a series, not a dataframe.

Still have no idea about the original error, but I was able to get it down to about half a second while using no apply:

Python code:
m = pd.Series(data=False, index=aapl_hist.loc["AAPL"].index)

for d in schedule.index:
    sched = schedule.loc[d]
    date_string = d.date().strftime("%Y-%m-%d")
    day_entries = m.loc[date_string : date_string] #Gets entries from a whole day. Passing a string was the easiest way to bypass timezone bs
    day_minutes = day_entries.between_time(sched.market_open.timetz(), sched.market_close.timetz(), inclusive="left")
    m[day_minutes.index] = True

aapl_filtered3 = aapl_hist.loc["AAPL"][m]
Kind of cheating because I think there is some other logic involved in the open_at_time() function from the market calendar lib.

I think I'm pretty happy with this as the general form though. Half a second still seems like kind of a lot, but it's good enough for my use/massive improvement from 2 minutes. I don't see a way to do it without a loop or apply.

Sad Panda
Sep 22, 2004

I'm a Sad Panda.

a dingus posted:

I'll also give Fluent Python a great endorsement. I read it when I was a late beginner and felt like it made me an intermediate just by how it connected so many dots. It was something I referenced often and still would if I were doing python full time.

If anyone knows the same type of book for JS/TS I'm all ears!

https://github.com/getify/You-Dont-Know-JS

Not the same but a maybe interesting book about JS.

QuarkJets
Sep 8, 2008

Apply is a low performance method, it's essentially the same as writing a big for loop. Vectorization is going to work a lot better, that's basically what you get with loc iirc

Foxfire_
Nov 8, 2010

FISHMANPET posted:

I'm writing a Python module, and I've gotten myself stuck into some circular dependencies. I've found some "basic" stuff about fixing that, but I seem to have gotten myself into a deeper pickle. Basically, I have a User class, and that User class has methods that will return a Group. And I have a Group class that has methods that will return a User. And I'm trying to define these in separate files, otherwise this is gonna be some massive 2000 line single-file module. I've finally just "thrown in the towel" so to speak and stopped importing at the top of the file, and instead am importing only when a function is called, but that doesn't feel great either.

So any tips or links on how to "design" my way out of this?
Python doesn't support that. It is one of its weird historical deficiencies (it is much easier to write an interpreter when all the module dependencies are acyclic)

It mostly doesn't fall apart because duck typing avoids most of the times types need to refer to each other. Type hints have increasingly less bad hacks bolted on to work around it as Python versions increase

For the rest of the situations, local imports like you're doing are the typical workaround. You shouldn't muck up your design to accommodate Python weirdness (unless the design actually has something wrong with it)

Slimchandi
May 13, 2005
That finger on your temple is the barrel of my raygun

haruspicy posted:

Whoever in here said that they didn’t really get Python until reading Fluent Python goddamn is that ever true, just picked it up

:yaycloud:

Zoracle Zed
Jul 10, 2001

FISHMANPET posted:

I'm writing a Python module, and I've gotten myself stuck into some circular dependencies. I've found some "basic" stuff about fixing that, but I seem to have gotten myself into a deeper pickle. Basically, I have a User class, and that User class has methods that will return a Group. And I have a Group class that has methods that will return a User. And I'm trying to define these in separate files, otherwise this is gonna be some massive 2000 line single-file module. I've finally just "thrown in the towel" so to speak and stopped importing at the top of the file, and instead am importing only when a function is called, but that doesn't feel great either.

Foxfire_ posted:

For the rest of the situations, local imports like you're doing are the typical workaround.

I usually do the local import like Foxfire_ suggests, especially if they can be constrained to a single method that 'converts' between the two types. The other option is, instead of having group.py 'from user import User', just do 'import user' and then refer to user.User when it's needed. That way you're not referencing any module members at the top level and avoid the circular import problem.

Precambrian Video Games
Aug 19, 2002



I still don't understand why a User class would have to have methods that return a Group, so until then:

QuarkJets posted:

You need to clean up this data model. If a Group contains Users, then a User does not need to call Group methods. Code that needs to interact with Group and User simultaneously (for instance, "given a User belonging to some Group, call a Group method") goes into a third module that imports from users and groups.

QuarkJets
Sep 8, 2008

Zoracle Zed posted:

I usually do the local import like Foxfire_ suggests, especially if they can be constrained to a single method that 'converts' between the two types. The other option is, instead of having group.py 'from user import User', just do 'import user' and then refer to user.User when it's needed. That way you're not referencing any module members at the top level and avoid the circular import problem.

Using locally-scoped imports sweeps the problem under the rug, you should use top-level imports and structure your import hierarchy to avoid circular imports

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
Sometimes there are legitimate reasons to model your data that way. Don't contort your data into some totally bullshit shape just to avoid a circular import.

That said, this particular shape (Groups and Users, where Groups contain Users and a User can be in many Groups) probably shouldn't be modelled that way. One pretty good way to do it would be to have Groups containing Users, and then a third component that supports looking up all the Groups for a particular User.

QuarkJets
Sep 8, 2008

A simple tree hierarchy is never a "totally bullshit" shape, whereas a circular hierarchy often is

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
A User object has a get_groups() method that will return all the Groups the User is a member of. A Group has a get_members() method that will return all Users of the Group. So a User object has no Group property, and a Group has no User property, but they each have methods that will call the constructor for the opposite object.

I've gotten things pretty well cleaned up with a combination of doing local imports and importing TYPE_CHECKING to enable my type annotations without needing full imports at the root of the file.

Jabor
Jul 16, 2010

#1 Loser at SpaceChem

QuarkJets posted:

A simple tree hierarchy is never a "totally bullshit" shape, whereas a circular hierarchy often is

If your data is modelled as a tree, being able to step from a child to its direct parent is incredibly useful, but requires a circular reference.

Precambrian Video Games
Aug 19, 2002



FISHMANPET posted:

A User object has a get_groups() method that will return all the Groups the User is a member of.

Do Users need to know what Groups they are a member of? If not, get_groups can take a User argument and go into a third container-of-Groups class (which it seems like you'll need or want anyway).

FISHMANPET posted:

... they each have methods that will call the constructor for the opposite object.

Do you really need a User to be able to construct a Group? And if so, can't you do it with, for example, a @classmethod constructor in Group? Not to harp on this if you have specific needs to do it circularly, but:

Jabor posted:

If your data is modelled as a tree, being able to step from a child to its direct parent is incredibly useful, but requires a circular reference.

FISHMANPET appears to be describing an N:N relationship, where I can see there might be some convenience in having two-way links but if there's a need to do things to Users depending on what Groups they're in, the logic should probably go in the appropriate Group (or third) class rather than in the User class.

QuarkJets
Sep 8, 2008

Jabor posted:

If your data is modelled as a tree, being able to step from a child to its direct parent is incredibly useful, but requires a circular reference.

I don't think that's true - for instance the Qt data model permits directly stepping to a parent (via a parent property that basically all Qt classes have), but the codebase doesn't tend to have circular references. In C++ this requires a generic class that all classes with parentage inherit from (in Qt that class is QObject), in Python you can either define a similar class that manages parent/child linkage or just not use type annotations and allow parent to be dynamically typed

Jabor
Jul 16, 2010

#1 Loser at SpaceChem

QuarkJets posted:

I don't think that's true - for instance the Qt data model permits directly stepping to a parent (via a parent property that basically all Qt classes have), but the codebase doesn't tend to have circular references. In C++ this requires a generic class that all classes with parentage inherit from (in Qt that class is QObject), in Python you can either define a similar class that manages parent/child linkage or just not use type annotations and allow parent to be dynamically typed

Both of those are worse than just having the circular reference.

QuarkJets
Sep 8, 2008

Jabor posted:

Both of those are worse than just having the circular reference.

If a User can belong to multiple Groups then the only type annotation that you need is probably `list` anyway - User doesn't need to see any Group code at all

Seventh Arrow
Jan 26, 2005

For various reasons well ok, job interview, I need to start learning about data structures and algorithms and all that outer-space leetcode stuff. Any recommendations on where to start?

Mind you, I suppose that's more of a "computer science" question than a "python" question, but I'll eventually have to implement this stuff in python anyways so eh.

Zoracle Zed
Jul 10, 2001

QuarkJets posted:

If a User can belong to multiple Groups then the only type annotation that you need is probably `list` anyway - User doesn't need to see any Group code at all

what? you'd still want it as list[Group].

QuarkJets
Sep 8, 2008

Zoracle Zed posted:

what? you'd still want it as list[Group].

Nah that's unnecessary and making the code structure worse

qsvui
Aug 23, 2003
some crazy thing

Seventh Arrow posted:

For various reasons well ok, job interview, I need to start learning about data structures and algorithms and all that outer-space leetcode stuff. Any recommendations on where to start?

Mind you, I suppose that's more of a "computer science" question than a "python" question, but I'll eventually have to implement this stuff in python anyways so eh.

Well you already mentioned leetcode. Cracking the Coding Interview is also often recommended.

Seventh Arrow
Jan 26, 2005

Thank you, but I guess what I meant was any resources where I can learn the underlying concepts behind data structures and algorithms. I don't want to blindly hack away at leetcode problems.

Granted, googling solutions to leetcode problems might help me gather a knowledge base about these kinds of concepts, but I was hoping for something more structured. I actually might hire a tutor on fiverr, so I will think about this a bit further.

Falcon2001
Oct 10, 2004

Eat your hamburgers, Apollo.
Pillbug

QuarkJets posted:

Nah that's unnecessary and making the code structure worse

Wait, expand on this: Type hinting is making the code worse? Because this was explicitly the example I was thinking of when reading earlier.

Like sure, you can just not have it hinted and have a docstring or something saying 'oh this returns a List of Groups' but I find this kind of a strongly odd statement to make.

I think it's totally fair to say that like, you shouldn't have deeply connected circular code, but if what we're talking about is basically just type hinting for parent/child/etc that's pretty reasonable IMO.

QuarkJets
Sep 8, 2008

Falcon2001 posted:

Wait, expand on this: Type hinting is making the code worse?

Not the type hinting specifically; in this example User and Group are in separate files, and Group already imports from user.py, so having User import from group.py introduces a circular dependency. That makes the code structure worse. If they were in the same file then this'd be okay but not great, because it'd be somewhat brittle

e: You can use the special TYPE_CHECKING variable so that the circular import only exists while typechecking, but that makes the code kind of ugly imo

QuarkJets fucked around with this message at 22:16 on Apr 15, 2023

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
Can you define what "brittle" means to you and why you think it's bad?

It's totally okay to have a "package" of closely-related files that depend on each other. You shouldn't feel like you need to combine them all into a single file just because you think mutual dependencies feel icky.

QuarkJets
Sep 8, 2008

Jabor posted:

Can you define what "brittle" means to you and why you think it's bad?

It's totally okay to have a "package" of closely-related files that depend on each other. You shouldn't feel like you need to combine them all into a single file just because you think mutual dependencies feel icky.

It means that the code is not resilient. Brittle code is easier to accidentally break when you're later trying to maintain it or add features to it. Circular dependencies are a known anti-pattern in software design because they create brittle code and should be avoided.

You don't have to take my word for it, either; Google's style guide describes circular imports for the sake of type-checking as a code smell and using locally-scoped imports for the sake of permitting a circular dependency creates a PEP8 violation.

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
Can you give an example of how having the return value properly typed as a List[Group] instead of a List[Any] makes your code "easier to accidentally break when you're later trying to maintain it"?

QuarkJets
Sep 8, 2008

Jabor posted:

Can you give an example of how having the return value properly typed as a List[Group] instead of a List[Any] makes your code "easier to accidentally break when you're later trying to maintain it"?

The typehinting is not a problem, the circular import is. The OP has already demonstrated one issue that it can cause, the code raises an exception when you implement the imports in one way but not in another. Now the code has been modified around preserving this circular hierarchy, throwing away PEP8 while creating code that sounds pretty brittle to me:

FISHMANPET posted:

A User object has a get_groups() method that will return all the Groups the User is a member of. A Group has a get_members() method that will return all Users of the Group. So a User object has no Group property, and a Group has no User property, but they each have methods that will call the constructor for the opposite object.

I've gotten things pretty well cleaned up with a combination of doing local imports and importing TYPE_CHECKING to enable my type annotations without needing full imports at the root of the file.

boofhead
Feb 18, 2021

just to clarify, since this is also something i've been scratching my head over but never had time/energy to research properly because i am a bad developer, but:

you're saying a better implementation would be something like:

Python code:
# user.py

from app.user_group import get_user_groups

class User():
	def get_groups() -> list:
		return get_user_groups(user_id=self.id)

# group.py

from app.user_group import get_group_users

class Group():
	def get_users() -> list:
		return get_group_users(group_id=self.id)

# user_group.py

from app.user import User
from app.group import Group

def get_user_groups(user_id: int) -> list[Group]:
	# do the thing
	return groups

def get_group_users(group_id: int) -> list[User]:
	# do the thing
	return users
or to omit the class from the list type entirely? i.e.

Python code:
def get_group_users(group_id: int) -> list:

SurgicalOntologist
Jun 17, 2004

In my opinion, typing list instead of List[Group] just to avoid the circular import is probably the worst possible solution. If you believe the circular relationship is a smell (which I'm on the fence about), well that hasn't been addressed, and you've basically given up on having your code well-typed. I find the recommendation to do that or List[Any] baffling, even if it comes from Google.

What I might do is something like this.

Python code:
# Original

class User:
    def get_groups(self) -> List[Group]:
        ...


class Group:
    users: List[User]

 
# Refactored

class User:
    ...


class Group:
    users: List[User]

    @classmethod
    def get_groups_of_user(cls, user: User) -> List[Group]:  # anyone know if there is a way to type this like List[cls] instead?
        ...


Basically, some_user.get_groups() may look natural but there's nothing wrong with Group.get_groups_of_user(some_user). To me, it's nice to have a simple class like User at the base of the tree with purely internal logic and no dependencies. It's groupability behavior can be in another class, whether that's Group or a third one, without losing expressability.

On the other hand, I'm partial to the argument that this is just a workaround and there's nothing wrong with the circular dependency (other than the fact that it's not allowed). Probably the actual best approach in these situations is to put them in the same file. And if the response is "that file would be too big!" then for sure the classes are too big and should be split. Maybe user.py and group.py is not actually the best way to split the logic into modules/classes. Especially with Protocols you can split different aspects of your objects' behaviors among classes rather than having big monster classes. So, my response when a colleague asks for help with a circular dependency problem is almost always "make your classes small enough that you are comfortable putting them together". If they are so tightly coupled, it makes sense that they be in the same module.

Edit:

I think the answer to my above question about typing the classmethod output is something like

Python code:
T = TypeVar('T')

class Group(Generic[T]):
    def __init__(self, items: Iterable[T]):
        self.items: List[T] = list(items)

    @classmethod
    def get_groups_by_item(cls, item: T) -> List[Group[T]]:
        ...


user_group = Group[User](users)
(I would probably also try to subclass from Sequence or Collection and get useful methods like __iter__ and __getitem__.)

Oh wait, that just abstracts the User part, not the Group part. Well, maybe useful, but I'm still not sure how to enforce that a subclass's get_groups_by_item can only return a list of itself rather than of a parent class.

Edit2: Ok I googled it, the answer is to use typing on cls. Getting complicated but maybe...
Python code:
T = TypeVar('T')
G = TypeVar('G', bound='Group')

class Group(Generic[T]):
    def __init__(self, items: Iterable[T]):
        self.items: List[T] = list(items)

    @classmethod
    def get_groups_by_item(cls: Type[G], item: T) -> List[G[T]]:
        ...


user_group = Group[User](users)
Admittedly that was a bit of a side curiosity question and that probably isn't necessary if the only thing you want to make groups of is users. Still, it shows that not only you can define users without them needing to know that they are groupable (by putting all group-related behavior on the grouping class), but you can even define grouping capabilities without needing to know anything about users specifically. Any behavior related purely to managing generic sub-items can go in Group and any behavior dependent on the fact that you are dealing with users specifically (if any) can go in another UserGroup class.

SurgicalOntologist fucked around with this message at 13:03 on Apr 16, 2023

QuarkJets
Sep 8, 2008

boofhead posted:

just to clarify, since this is also something i've been scratching my head over but never had time/energy to research properly because i am a bad developer, but:

you're saying a better implementation would be something like:

I'm thinking about this post:

FISHMANPET posted:

A User object has a get_groups() method that will return all the Groups the User is a member of. A Group has a get_members() method that will return all Users of the Group. So a User object has no Group property, and a Group has no User property, but they each have methods that will call the constructor for the opposite object.

I've gotten things pretty well cleaned up with a combination of doing local imports and importing TYPE_CHECKING to enable my type annotations without needing full imports at the root of the file.

The multiple local imports are a problem imo. A User should not be calling the constructor of Group, this is prone to issues and is a pain to refactor; when you look at this in terms of technical debt it's actually just worse than having both classes in the same file. Having every User keep track of its groups as an attribute or property would be a bit better, and then you could just slap the TYPE_CHECKING variable at the type of 1 of the files without having to resort to local imports. Note that User cannot create a Group here, but its list of parents is fully typed.

Python code:
# user.py
import typing
if typing.TYPE_CHECKING:
    from .group import Group

class User:
    def __init__(self, name: str):
        self.name = name
        self.parents: list['Group'] = []

    def assign_parent(self, group: 'Group'):
        self.parents.append(group)


# group.py
from .user import User

class Group:
    def __init__(self, name: str):
        self.name = name
    
    def create_user(self, name: str) -> User:
        user = User(name)
        user.assign_parent(self)
        return user
This is a common approach and leagues better than the proposed solution. If I saw it in a PR I wouldn't feel the need to complain. There are still some downsides to this, however; for instance dependency injection has become impossible, and our minimally-separable unit is still the combination of User and Group. What if we want a User that has other types of parents besides Group? What if we want Users that are not allowed to have parents? What if we want new types of groups that have no users? These classes aren't separable at all, but at least we've improved it by not having any local imports

What are some alternatives? Presumably these classes don't just exist in a vacuum, you could create an interface or a mediator that tracks the linkage between instances of these classes instead of using a 2-way coupling in the class definitions.

QuarkJets fucked around with this message at 20:41 on Apr 16, 2023

Falcon2001
Oct 10, 2004

Eat your hamburgers, Apollo.
Pillbug

QuarkJets posted:

I'm thinking about this post:

The multiple local imports are a problem imo. A User should not be calling the constructor of Group, this is prone to issues and is a pain to refactor; when you look at this in terms of technical debt it's actually just worse than having both classes in the same file. Having every User keep track of its groups as an attribute or property would be a bit better, and then you could just slap the TYPE_CHECKING variable at the type of 1 of the files without having to resort to local imports. Note that User cannot create a Group here, but its list of parents is fully typed.

Python code:
# user.py
import typing
if typing.TYPE_CHECKING:
    from .group import Group

class User:
    def __init__(self, name: str):
        self.name = name
        self.parents: list['Group'] = []

    def assign_parent(self, group: 'Group'):
        self.parents.append(group)


# group.py
from .user import User

class Group:
    def __init__(self, name: str):
        self.name = name
    
    def create_user(self, name: str) -> User:
        user = User(name)
        user.assign_parent(self)
        return user
This is a common approach and leagues better than the proposed solution. If I saw it in a PR I wouldn't feel the need to complain. There are still some downsides to this, however; for instance dependency injection has become impossible, and our minimally-separable unit is still the combination of User and Group. What if we want a User that has other types of parents besides Group? What if we want Users that are not allowed to have parents? What if we want new types of groups that have no users? These classes aren't separable at all, but at least we've improved it by not having any local imports

What are some alternatives? Presumably these classes don't just exist in a vacuum, you could create an interface or a mediator that tracks the linkage between instances of these classes instead of using a 2-way coupling in the class definitions.

FWIW: This example is about how I would have approached it; I guess I misunderstood some of the distinctions here.

QuarkJets
Sep 8, 2008

I also want to clarify the terminology a little, I wonder if that's a source of confusion; when I see "local import" I think "an import statement inside of a function or method, instead of at the top of a file".

Foxfire_
Nov 8, 2010

Mutual dependencies are common, and something any more-than-toy language has to deal with.

Python's solution for them is to combine
- assume that most code is inside a function made by a def or lambda
and
- the language doesn't resolve names in functions until they actually execute. Until then, they are just saved strings

Those two combine to make an ersatz two-pass parser that mostly avoids the programmer having to think about it or deal with forward declarations, but is still very easy to implement (which is important because at that time, Python is still Guido van Rossum's one-man hobby project).

Python import statements are much closer to dumb C textual #includes than they are to imports in something like Java/C#/Rust.

In code like
Python code:
 1: class Foo:
 2:  def foo():
 3:   ...
 4:   some_bar_instance.bar()
 5:   
 6:  def foofoo()
 7:    ...
 8:
 9: class Bar:
10:   ...
what the interpreter actually does is:
- At (1), start constructing a new class object. It isn't accessible from anywhere yet, trying to access Foo will give a NameError
- At (2), start defining a new function object. It isn't accessible from anywhere yet either
- At (4), in the function's code, save that it's going to call "bar" (that literal string) on some_bar_instance. Bar doesn't exist at all yet, let alone exist with a bar member
- At (5), finish the function object being constructed. Save a reference to it in the class object under construction's attributes table under the "foo" string key
- At (8), finish the class object being constructed. Save a reference to it in this module's attributes table under the "Foo" string key. Lookups for Foo will succeed after this point

The interpreter doesn't care that names inside functions don't point to anything at the time they were defined, it's only an error if they are still invalid at the time they are run.

This mostly works as well as real two-pass parsing, except for all the times it doesn't and the programmer has to work around it.

One situation where it doesn't work at all is type hint annotations, because those are code not hidden inside a function. They run during definition of the class/function and names in them need to exist at that point. Mutual reference or self referencing names won't.

The language has gotten a few hacks layered on top to deal with that:
- typing.TYPE_HINTING lets code do different things depending on who is consuming it

- If an annotation evaluates to a string, somebody using that annotation is supposed to eval() it to get the actual value. So you can do
def Foo() -> "SomeTypeThatDoesntExistYet".

- PEP563 changed the language so that instead of evaluating annotation code, the interpreter replaces them with a string that, when eval()-ed, will return the original annotation. Basically it automates that string wrapping from the earlier hack. You enable this with from __future__ import annotations. This was added in Python 3.7 and supposed to become default in Python 3.10, but was delayed because the PEP649 hack seemed potentially better

- PEP649 is a proposed change to use a hidden function to delay evaluation of annotations until the first time somebody access the __annotations__ object. It's better because it avoids some breaking changes to the names that are available to annotation code


If you're using Python 3.7+ and not doing anything fancy, you should probably be from __future__ import annotations instead of testing typing.TYPE_CHECKING

Seventh Arrow
Jan 26, 2005

So it looks like it will be a while before I can nail live coding tests. I had one today to take the following list:

code:
list=[5,2,7,1,4,6,3,9]
and arrange the odd numbers so that they are in order, while the even numbers are left in place.

This tangled mess was my initial answer:

code:
list=[5,2,7,1,4,6,3,9]
odd = []
for l in list:
  if l % 2 != 0:
    odd.append(l)
    sorted_odds = sorted(odd)
    # print(sorted_odds)
    list2 = list + sorted_odds
    print(list2)
which of course printed out a line for every step of iteration.

I corrected it as best as I could:

code:
list = [5,2,7,1,4,6,3,9]
odd = []
for l in list:
  if l % 2 != 0:
    odd.append(l)
sorted_odds = sorted(odd)
list2 = list + sorted_odds
print(list2)
but then I realized that I could have used a list comprehension (maybe two) :doh:

Good thing they didn't ask a leetcode-style question. Oh well, I'm improving!

SurgicalOntologist
Jun 17, 2004

I just can't help myself...

Python code:
def isodd(n: int) -> bool:
    return bool(n % 2)


def sort_odds_inplace(inputs: list[int]) -> list[int]:
    odds = iter(sorted(filter(isodd, inputs)))
    return [next(odds) if isodd(n) else n for n in inputs]


assert sort_odds_inplace([5,2,7,1,4,6,3,9]) == [1, 2, 3, 5, 4, 6, 7, 9]
Of course, ask me to do it live and I'd probably completely crack, I've never had to do a live coding test.

Adbot
ADBOT LOVES YOU

boofhead
Feb 18, 2021

ahh i always forget to use filter and all the extra QOL poo poo in python

i, too, couldnt help myself

Python code:
source_list = [5,2,7,1,4,6,3,9]

evens_in_place = [n if n % 2 == 0 else None for n in source_list]
# [None, 2, None, None, 4, 6, None, None]

odds_sorted = sorted([n for n in source_list if n % 2 != 0])
# [1, 3, 5, 7, 9]

final = [n if n else odds_sorted.pop(0) for n in evens_in_place]

final
# [1, 2, 3, 5, 4, 6, 7, 9]
e: ive never done like a live coding test beyond 5 minutes of basic SQL maybe 6 years ago for a PM position, you can still google and check documentation, right? like if you need to check syntax or a method/class name or whatever, theyre not still doing that thing where you have to commit it to memory because real programmers dont have internet access or computers and do all their work from a cave in the woods. all they need is a bucket, a gazelle carcass, and a tape recorder to dictate their commits in

boofhead fucked around with this message at 07:53 on Apr 19, 2023

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply